<?php

namespace W3;

use W3\Db\Query;

/**
 * 数据库查询语句构建类
 *
 * @author edikud
 * @date 2022/10/22
 * @copyright Copyright (c) 2022 W3 (http://www.mcooo.com)
 * @license GNU General Public License 2.0
 */
class Db
{
    /**
     * 单例句柄
     *
     * @access protected
     * @var Db
     */
    protected static $instance;
	
    /**
     * _executor  
     * 
     * @var AdapterInterface
     * @access private
     */
    private $_adapter;
	
	/**
	 * 主库数据源 (主库 原则上仅仅承载着 "写" 数据的功能)
	 *
	 */
	private $master;

	/**
	 * 从库数据源 (从库 仅仅承载着 "读" 数据的功能)
	 * 
	 */
	private $slaver;
	
	# 数据库适配器支持
    private static $drivers = ['mysql'=>'W3\Db\Mysql'];
	
    /**
     * 配置
     *
     * @var array
     */
    private $config;

    /**
     * 前缀
     *
     * @access private
     * @var string
     */
    private $_prefix;

    /**
     * 适配器名称
     *
     * @access private
     * @var string
     */
    private $_adapterName;
	
    /**
     * Log
     *
     * @var array
     */
    public static $log = [];

    /**
     * 设置默认数据库对象
     *
     * @access public
     * @param Db $db 数据库对象
     * @return void
     */
    public static function setInstance(Db $db)
    {
        self::$instance = $db;
    }

    /**
     * 获取数据库实例化对象
     * 用静态变量存储实例化的数据库对象,可以保证数据连接仅进行一次
     *
     * @return Db
     * @throws Exception
     */
    public static function instance(): Db
    {
        if (empty(self::$instance)) {
            /** Exception */
            throw new Exception('Missing Database Object');
        }

        return self::$instance;
    }

    /**
     * 获取适配器名称
     *
     * @access public
     * @return string
     */
    public function adapterName(): string
    {
        return $this->_adapterName;
    }
	
    /**
     * 获取表前缀
     *
     * @access public
     * @return string
     */
    public function prefix(): string
    {
        return $this->_prefix;
    }
	
    /**
     * 数据库类构造函数
     *
     * @param mixed $adapterName 适配器名称
     * @param string $prefix 前缀
     * @throws Exception
     */
    public function __construct(string $name, string $prefix = null)
    {
		$adapter = isset(static::$drivers[$name]) ? static::$drivers[$name] : '';
        if (!$adapter) {
            throw new Exception("Adapter '{$name}' not found");
        }
		
        /** 获取适配器名称 */
        $this->_adapterName = $adapter;
		
        $this->_prefix = $prefix;

        $this->config = [
            'read' => [],
            'write' => []
        ];

        $this->_adapter = new $adapter();
    }

    public static function drivers(?string $name = NULL, ?string $class = NULL)
    {
        if (NULL === $name) {
            return static::$drivers;
        }
		
        static::$drivers[$name] = $class;
    }

    /**
     * getConfig
     *
     * @param string $op
     *
     * @return Config
     * @throws Exception
     */
    public function config(string $op): Config
    {
        if (empty($this->config[$op])) {

            throw new Exception('Missing Database Connection');
        }

        $key = array_rand($this->config[$op]);
        return $this->config[$op][$key];
    }

    /**
     * 重置连接池
     *
     * @return void
     */
    public function reset()
    {
        $this->master = null;
		$this->slaver = null;
    }

    public function master()
    {
        if (!isset($this->master)) {
            $config = $this->config('write');
            $this->master = $this->_adapter->connect($config);
        }

        return $this->master;
    }

    public function slaver()
    {
        if (!isset($this->slaver)) {
			
			if(empty($this->config['read'])){
				$this->slaver = $this->master();
				
			} else {
                $config = $this->config('read');
                $this->slaver = $this->_adapter->connect($config);
			}
        }

        return $this->slaver;
    }

    /**
     * 为多数据库提供支持
     *
     * @access public
     * @param Db $db 数据库实例
     * @param integer $op 数据库操作
     * @return void
     */
    public function addServer(array $config, bool $op = true)
    {
		/** 将连接放入池中 */
		$this->config[$op ? 'write' : 'read'][] = Config::make($config);
		
		$this->reset();
    }

    public function adapter()
    {
        return $this->_adapter;
    }
	
    /**
     * 获取SQL词法构建器实例化对象
     *
     * @return Query
     */
    public function sql(): Query
    {
        return new Query($this);
    }
	
    /**
     * 选择查询字段
     *
     * @access public
     * @param mixed $field 查询字段
     * @return Query
     */
    public function select(...$args): Query
    {   
        //$args = func_get_args();
        //return call_user_func_array([$this->sql(), 'select'], $args ?: ['*']);
	    
		empty($args) && $args = ['*'];
		return $this->sql()->select(...$args);
    }
	
    /**
     * 更新记录操作(UPDATE)
     *
     * @param string $table 需要更新记录的表
     * @return Query
     */
    public function update(string $table): Query
    {
        return $this->sql()->update($table);
    }

    /**
     * 删除记录操作(DELETE)
     *
     * @param string $table 需要删除记录的表
     * @return Query
     */
    public function delete(string $table): Query
    {
        return $this->sql()->delete($table);
    }

    /**
     * 插入记录操作(INSERT)
     *
     * @param string $table 需要插入记录的表
     * @return Query
     */
    public function insert(string $table): Query
    {
        return $this->sql()->insert($table);
    }	
	
    /**
     * 执行查询语句
     *
     * @param mixed $query 查询语句或者查询对象
     * @param int $op 数据库读写状态
     * @param string $action 操作动作
     * @return mixed
     * @throws Query
     */
    public function query(string $sql): Query
    {
		return $this->sql()->sql($sql);
    }

    /**
     * Magic method for calling other Query methods
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     * @throws \ErrorException
     */
    public function __call(string $method, $arguments)
    {
		return $this->master()->{$method}(...$arguments);
    }
	
    /**
     * SQL语句调试.
     * debug===true时, 默认记录在sql.log
     *
     * @param string $sql <code>select * from table where id = ?</code>
     * @param float $execute_time 当前sql执行时间
     *
     * @return void
     */
    public static function debug($sql, $params = array(), $execute_time = 0)
    {
        if (Config::instance()->debug) {
            $sql = preg_replace_callback('/[?]/', function ($k) use($params) {
                static $i = 0;
                return sprintf("'%s'", $params[$i++]);
            }, $sql);
            if (count(static::$log) < 1000) {
                static::$log[] = ' <font color="red">[time:' . number_format($execute_time, 4) . 's]</font> ' . $sql;
            }
        }
    }
}
